package com.doctcloud.hisinfo.common.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.PutMappingRequest;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.doctcloud.common.core.utils.EncodingConverter;
import com.doctcloud.common.core.utils.StringUtils;
import com.doctcloud.common.core.utils.bean.CommonDoVoConverter;
import com.doctcloud.common.elasticsearch.model.EsPageResult;
import com.doctcloud.common.elasticsearch.service.ElasticSearchService;
import com.doctcloud.hisinfo.common.mapper.DeptDictDoMapper;
import com.doctcloud.hisinfo.common.request.DeptParm;
import com.doctcloud.hisinfo.common.service.IDeptDescInfoService;
import com.doctcloud.hisinfo.common.service.IDeptDictService;
import com.doctcloud.hisinfo.histable.document.DeptDescInfoDo;
import com.doctcloud.hisinfo.histable.domain.comm.DeptDictDo;
import com.doctcloud.hisinfo.histable.document.FileInfoDo;
import com.doctcloud.hisinfo.histable.domain.vo.DeptDictVo;
import com.doctcloud.hisinfo.histable.domain.vo.DeptTreeSelect;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.doctcloud.common.core.constant.CacheConstants.*;


@Service
@Slf4j
public class DeptDictServiceImpl extends ServiceImpl<DeptDictDoMapper, DeptDictDo> implements IDeptDictService {
    private static final String INDEX_NAME = "his_common_dept_dict";
    @Resource
    private ElasticSearchService elasticSearchService;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ElasticsearchClient esClient;
    @Resource
    private IDeptDescInfoService deptDescInfoService;
    /**
     * 初始化
     * @throws IOException
     */
    @PostConstruct
    public void ini() throws IOException {
        RLock syncLock = redissonClient.getLock(HIS_SYNC_LOCK_PREFIX + INDEX_NAME);
        boolean isSyncLocked = false;
        try {
            isSyncLocked = syncLock.tryLock(HIS_LOCK_WAIT_SECONDS, HIS_LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
            if (!isSyncLocked) {
                log.info("已有线程正在执行 {} 同步操作，当前线程退出", INDEX_NAME);
                return;
            }
            elasticSearchService.deleteIndex(INDEX_NAME);
            initIndexAndMapping();
            log.info("开始从数据库同步科室数据到Elasticsearch...");
            List<DeptDictDo> deptList = listDeptsDB();
            if (deptList == null || deptList.isEmpty()) {
                log.info("数据库中没有科室数据，同步结束。");
                return;
            } else {
                int i = 0;
                while (i<10){
                    log.info("【调试】从数据库读取的第一个科室名称: {}", deptList.get(i).getDeptName());
                    i++;
                }
                /**
                 * 字符集转换 GBK->UFT-8
                 */
                EncodingConverter.gbkToUtf8(deptList);

                for (DeptDictDo dept:deptList){
                    String deptDescDetail = "";
                    String deptBuckets = "";
                    List<FileInfoDo> fileInfos = new ArrayList<>();
                    DeptDescInfoDo deptInfo = deptDescInfoService.getDeptInfoByCode(dept.getDeptCode());
                    if (!ObjectUtils.isEmpty(deptInfo)) {
                        if (!ObjectUtils.isEmpty(deptInfo.getFileInfos())) {
                            fileInfos = deptInfo.getFileInfos();

                        }
                        if (!StringUtils.isEmpty(deptInfo.getDeptDescDetail())){
                            deptDescDetail = deptInfo.getDeptDescDetail();
                        }
                        if (!StringUtils.isEmpty(deptInfo.getBuckets())){
                            deptBuckets = deptInfo.getBuckets();
                        }
                    }
                    dept.setFileInfos(fileInfos);
                    dept.setDeptDescDetail(deptDescDetail);
                    dept.setBuckets(deptBuckets);
                }
            }

            /**
             * 为方便反序列 OBJECT DO -> VO
             */
            CommonDoVoConverter<DeptDictDo, DeptDictVo> deptConverter = CommonDoVoConverter.create();
            List<DeptDictVo> deptDictVos = deptConverter.doListToVoList(deptList, DeptDictVo.class);

            Map<String, DeptDictVo> deptMap = new HashMap<>(deptDictVos.size());
            for (DeptDictVo dept : deptDictVos) {
                deptMap.put(dept.getDeptCode(), dept);
            }
            elasticSearchService.bulkSave(INDEX_NAME, deptMap);
            log.info("科室数据同步完成，共 [{}] 条。", deptList.size());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("同步过程被中断", e);
        } finally {
            releaseLock(syncLock, isSyncLocked);
        }
    }
    /**
     * 更新科室字典
     * @param deptDictDo 科室数据对象
     * @return
     * @throws IOException
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public int updateDeptDict(DeptDictDo deptDictDo) throws IOException {
        // 1. 前置校验
        if (deptDictDo == null) {
            log.error("科室不能为空，更新失败");
            return 0;
        }
        if (StringUtils.isEmpty(deptDictDo.getDeptName()) || StringUtils.isEmpty(deptDictDo.getDeptCode()) || StringUtils.isEmpty(deptDictDo.getHospitalId())){
            log.error("科室要素[科室名称、科室代码、医院标识]不得为空，更新失败");
            return 0;
        }
        // 2. 定义分布式锁键
        String lockKey;
        lockKey = HIS_SYNC_LOCK_PREFIX + "dept_update_" + deptDictDo.getDeptCode();
        RLock updateLock = redissonClient.getLock(lockKey);
        DeptDescInfoDo origDeptInfo = null;
        DeptDictDo origDept = null;
        boolean isUpdateLocked = false;
        int retryCount = 0;
        try {
            // 3. 循环重试获取锁
            while (retryCount < HIS_CACHE_WAIT_RETRIES) {
                isUpdateLocked = updateLock.tryLock(HIS_LOCK_WAIT_SECONDS, HIS_LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
                if (isUpdateLocked) {
                    break;
                }
                retryCount++;
                if (retryCount >= HIS_CACHE_WAIT_RETRIES) {
                    log.info("科室[{}({})]获取锁失败，已重试{}次，当前线程退出", deptDictDo.getDeptName(), deptDictDo.getDeptCode(), HIS_CACHE_WAIT_RETRIES);
                    return 0;
                }
                log.debug("科室[{}({})]第{}次获取锁失败，{}ms后重试", deptDictDo.getDeptName(), deptDictDo.getDeptCode(), retryCount, HIS_CACHE_WAIT_INTERVAL_MS);
                TimeUnit.MILLISECONDS.sleep(HIS_CACHE_WAIT_INTERVAL_MS);
            }
            // 4.业务
            initIndexAndMapping();
            origDept = baseMapper.selectDeptByCodeDB(deptDictDo.getDeptCode());
            // 新增：原数据为空时，说明科室不存在，无需更新
            if (origDept == null) {
                log.error("科室[{}({})]不存在，更新失败", deptDictDo.getDeptName(), deptDictDo.getDeptCode());
                return 0;
            }
            origDeptInfo = deptDescInfoService.getDeptInfoByCode(deptDictDo.getDeptCode());
            // 新增：避免 origDeptInfo 为 null 导致 NPE
            if (origDeptInfo != null) {
                origDept.setDeptDescDetail(origDeptInfo.getDeptDescDetail());
                origDept.setBuckets(origDeptInfo.getBuckets());
                origDept.setFileInfos(origDeptInfo.getFileInfos());
            }
            // 1. 数据库更新
            int updateCount = baseMapper.updateDeptDict(deptDictDo);
            // 2. MongoDB 保存
            deptDescInfoService.createOrUpdateByDeptDict(deptDictDo);
            // 3. ES 保存
            elasticSearchService.save(INDEX_NAME, deptDictDo.getDeptCode(), deptDictDo);
            log.info("科室 [{}] 已同步更新到数据库和Elasticsearch", deptDictDo.getDeptName());;
            return updateCount;
        } catch (InterruptedException  e) {
            // 手动回滚 MongoDB（删除已保存的记录）
            deptDescInfoService.createOrUpdateByDeptDict(origDept);
            // 手动回滚 ES（删除已保存的记录）
            elasticSearchService.save(INDEX_NAME, origDept.getDeptCode(),origDept);
            Thread.currentThread().interrupt();
            log.error("更新科室[{}({})]时线程被中断", deptDictDo.getDeptName(), deptDictDo.getDeptCode(), e);
            return 0;
        }
    }
    /**
     * 新增科室
     * @param dept 科室数据对象
     * @return
     * @throws IOException
     */
    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public int addDeptDict(DeptDictDo dept) throws IOException {
        // 1. 前置校验
        if (dept == null || StringUtils.isEmpty(dept.getDeptCode())) {
            log.error("科室代码不能为空，添加失败");
            return 0;
        }
        if (StringUtils.isEmpty(dept.getHospitalId())) {
            log.error("医院标识不能为空，添加失败");
            return 0;
        }
        // 2. 定义分布式锁键（锁粒度：deptCode（优先））
        String lockKey;
        lockKey = HIS_SYNC_LOCK_PREFIX + "dept_add_" + dept.getDeptCode();
        RLock addLock = redissonClient.getLock(lockKey);
        boolean isAddLocked = false;
        int retryCount = 0; // 重试计数器
        try {
            // 3. 尝试获取锁（等待时间、持有时间与初始化方法一致，保持配置统一）
            // 3. 循环重试获取锁（最多重试 HIS_CACHE_WAIT_RETRIES 次）
            while (retryCount < HIS_CACHE_WAIT_RETRIES) {
                isAddLocked = addLock.tryLock(HIS_LOCK_WAIT_SECONDS, HIS_LOCK_HOLD_SECONDS, TimeUnit.SECONDS);
                if (isAddLocked) {
                    break; // 获取锁成功，跳出循环
                }
                // 获取锁失败，重试计数器+1，判断是否还能重试
                retryCount++;
                if (retryCount >= HIS_CACHE_WAIT_RETRIES) {
                    log.info("科室[{}]获取锁失败，已重试{}次，当前线程退出", dept.getDeptName(), HIS_CACHE_WAIT_RETRIES);
                    return 0;
                }// 未达到最大重试次数，等待间隔后重试
                log.debug("科室[{}]第{}次获取锁失败，{}ms后重试", dept.getDeptName(), retryCount, HIS_CACHE_WAIT_INTERVAL_MS);
                TimeUnit.MILLISECONDS.sleep(HIS_CACHE_WAIT_INTERVAL_MS);
            }
            // 4. 再次校验唯一性（加锁后二次校验，避免并发误判）
            if (checkDeptNameUnique(dept)) {
                log.warn("科室名称 [{}]，科室代码[{}] 已存在，添加不允许新增", dept.getDeptName(),dept.getDeptCode());
                return 0;
            }
            // 5. 原有业务逻辑（初始化索引、赋值、保存）
            initIndexAndMapping();
            dept.setCreateDate(new Date());
            dept.setCreateBy(dept.getCreateBy());
            // 6. 数据库、MongoDB、ES 保存
            int insertCount = baseMapper.insertDeptDict(dept);
            deptDescInfoService.createOrUpdateByDeptDict(dept);
            elasticSearchService.save(INDEX_NAME, dept.getDeptCode(), dept);
            log.info("科室 [{}] 已成功添加到数据库和Elasticsearch", dept.getDeptName());
            return insertCount;
        } catch (InterruptedException e) {
            // 手动回滚 MongoDB（删除已保存的记录）
            deptDescInfoService.createOrUpdateByDeptDict(dept);
            // 手动回滚 ES（删除已保存的记录）
            elasticSearchService.delete(INDEX_NAME, dept.getDeptCode());
            Thread.currentThread().interrupt();
            log.error("新增科室[{}]时线程被中断", dept.getDeptName(), e);
            return 0;
        } finally {
            // 7. 释放锁（复用原有 releaseLock 方法，避免重复代码）
            releaseLock(addLock, isAddLocked);
        }
    }
    /**
     * 通过[科室名称][科室代码]复合精准查询，不分页
     * @param dept 包含查询条件的科室对象（需设置 deptName）
     * @return
     * @throws IOException
     */
    @Override
    public List<DeptDictDo> searchDeptDict(DeptDictDo dept) throws IOException {
        initIndexAndMapping();
        if (dept == null || StringUtils.isEmpty(dept.getDeptName())) {
            return Collections.emptyList();
        }
        List<String> searchFields = Arrays.asList("deptName", "deptCode");
        return elasticSearchService.multiSearch(INDEX_NAME, searchFields, dept.getDeptName(), DeptDictDo.class);
    }
    /**
     * 通过[科室名称][输入码]复合模糊查询 不分页
     * @param dept 包含查询条件的科室对象（需设置 deptName，且长度≥2）
     * @return
     * @throws IOException
     */
    @Override
    public List<DeptDictDo> searchDeptsWithFuzzy(DeptDictDo dept) throws IOException {
        initIndexAndMapping();
        if (dept == null) {
            return Collections.emptyList();
        }
        String deptName = StringUtils.trimAll(dept.getDeptName());
        if (StringUtils.isEmpty(deptName) || deptName.length() < 2) {
            return Collections.emptyList();
        }
        List<String> searchFields = Arrays.asList("deptName", "inputCode");
        return elasticSearchService.fuzzyMultiSearch(INDEX_NAME, searchFields, deptName, DeptDictDo.class);
    }
    /**
     * 检查科室名称是否存在，判断科室实例是否为新增还是更新
     * @param dept 待检查的科室对象（需设置 deptName）
     * @return false - 可以新增 true - 是更新
     * @throws IOException
     */
    @Override
    public boolean checkDeptNameUnique(DeptDictDo dept)  {
        if (dept == null || StringUtils.isEmpty(dept.getDeptName())) {
            return false;
        }
        List<DeptDictDo> deptsFromDb = baseMapper.selectDepts(dept);
        if (deptsFromDb == null || deptsFromDb.isEmpty()) {
            return false;
        }
        if (!StringUtils.isEmpty(dept.getDeptCode())) {
            return deptsFromDb.stream().anyMatch(d -> !d.getDeptCode().equals(dept.getDeptCode()));
        }
        return true;
    }
    @Override
    public List<DeptDictDo> selectDeptByDB(DeptDictDo dept) {
        return this.baseMapper.selectDepts(dept);
    }
    @Override
    public DeptDictDo selectDeptByCode(String deptCode) {
        // 1. 先从 ES 查询
        DeptDictDo objDept = (DeptDictDo)elasticSearchService.get(INDEX_NAME, deptCode,DeptDictDo.class);
        // 2. ES 查不到时，从数据库查询并同步到 ES
        if (ObjectUtils.isEmpty(objDept)){
            log.debug("ES 中未查询到科室[{}]，从数据库加载并同步到 ES", deptCode);
            // 2.1 从数据库查询基础数据
            objDept = this.baseMapper.selectDeptByCodeDB(deptCode);
            // 2.2 数据库也查不到时，直接返回 null
            if (ObjectUtils.isEmpty(objDept)) {
                log.warn("数据库中也未查询到科室[{}]", deptCode);
                return null;
            }
            // 2.3 补全科室描述信息
            String deptFileUrl = "";
            String deptDescDetail = "";
            String deptBuckets = "";
            List<FileInfoDo> fileInfoDos = new ArrayList<>();
            DeptDescInfoDo deptInfo = deptDescInfoService.getDeptInfoByCode(objDept.getDeptCode());
            if (!ObjectUtils.isEmpty(deptInfo)) {
                if (!ObjectUtils.isEmpty(deptInfo.getFileInfos())) {
                    fileInfoDos = deptInfo.getFileInfos();
                }
                if (!StringUtils.isEmpty(deptInfo.getDeptDescDetail())) {
                    deptDescDetail = deptInfo.getDeptDescDetail();
                }
                if (!StringUtils.isEmpty(deptInfo.getBuckets())) {
                    deptBuckets = deptInfo.getBuckets();
                }
            }
            objDept.setFileInfos(fileInfoDos);
            objDept.setDeptDescDetail(deptDescDetail);
            objDept.setBuckets(deptBuckets);
            // 2.4 关键新增：将补全后的科室数据同步到 ES（确保后续查询可从 ES 获取）
            try {
                // 先初始化 ES 索引（避免索引不存在导致保存失败）
                initIndexAndMapping();
                // 保存到 ES（以 deptCode 为唯一 ID）
                elasticSearchService.save(INDEX_NAME, objDept.getDeptCode(), objDept);
                log.debug("科室[{}]已从数据库同步到 ES", deptCode);
            } catch (IOException e) {
                // 捕获 ES 保存异常，避免影响主流程（数据库数据已获取，仅 ES 同步失败）
                log.error("科室[{}]同步到 ES 失败", deptCode, e);
                // 此处不抛出异常，仅打印日志，确保能正常返回数据库查询到的结果
            }
        }
        return objDept;
    }

    /**
     * 构建前端所需要树结构
     *
     * @param depts 部门列表
     * @return 树结构列表
     */
    @Override
    public List<DeptDictDo> buildDeptTree(List<DeptDictDo> depts) {
        List<DeptDictDo> returnList = new ArrayList<DeptDictDo>();
//        List<String> tempList = depts.stream().map(DeptDictDo::getDeptId).collect(Collectors.toList());
//        for (DeptDictDo dept : depts)
//        {
//            // 如果是顶级节点, 遍历该父节点的所有子节点
//            if (!tempList.contains(dept.getParentId()))
//            {
//                recursionFn(depts, dept);
//                returnList.add(dept);
//            }
//        }
//        if (returnList.isEmpty())
//        {
//            returnList = depts;
//        }
        return returnList;
    }

    @Override
    public List<DeptTreeSelect> buildDeptTreeSelect(List<DeptDictDo> depts) {
        List<DeptDictDo> deptTrees = buildDeptTree(depts);
        return deptTrees.stream().map(DeptTreeSelect::new).collect(Collectors.toList());
    }

    /**
     * 分页模糊查询科室（基于 Elasticsearch）
     * @param dept 分页模糊查询参数（需设置 deptName、currentPage、pageSize）
     * @return
     */
    @Override
    public IPage<DeptDictDo> pageESDeptList(DeptParm dept) {
        try {
            initIndexAndMapping();
            long current = ObjectUtils.isEmpty(dept.getCurrentPage()) ? 1 : dept.getCurrentPage();
            long size = ObjectUtils.isEmpty(dept.getPageSize()) ? 10 : dept.getPageSize();
            if (StringUtils.isEmpty(dept.getDeptName())) {
                return new Page<>(current, size, 0);
            }
            List<String> searchFields = Arrays.asList("deptName", "deptAlias", "deptCode");
            EsPageResult<DeptDictDo> esResult = elasticSearchService.fuzzyMultiSearchWithTotal(
                    INDEX_NAME, searchFields, dept.getDeptName(), (int) current, (int) size, DeptDictDo.class);
            IPage<DeptDictDo> resultPage = new Page<>(current, size);
            resultPage.setRecords(esResult.getRecords());
            resultPage.setTotal(esResult.getTotal());
            return resultPage;
        } catch (IOException e) {
            log.error("ES分页查询失败", e);
            return new Page<>();
        }
    }
    /**
     * 通过科室代码删除科室字典
     * @param deptCode 由于表中无id，当前的id 组成 hospital_id||dept_code
     * @return
     * @throws IOException
     */
    @Override
    @GlobalTransactional
    public boolean deleteDeptDict(String deptCode) throws IOException {
        // 1. 前置校验：科室代码不能为空
        if (StringUtils.isEmpty(deptCode)) {
            log.error("删除科室ID不能为空");
            return false;
        }
        // 2. 定义分布式锁键（复用缓存锁前缀，锁粒度：deptCode）
        String lockKey = HIS_CACHE_LOCK_PREFIX + "dept_delete_" + deptCode;
        RLock deleteLock = redissonClient.getLock(lockKey);
        boolean isDeleteLocked = false;
        int retryCount = 0; // 重试计数器
        try {
            // 3. 循环重试获取锁（与新增/更新保持一致的重试策略）
            while (retryCount < HIS_CACHE_WAIT_RETRIES) {
                isDeleteLocked = deleteLock.tryLock(HIS_LOCK_WAIT_SECONDS, TimeUnit.SECONDS);
                if (isDeleteLocked) {
                    break;
                }
                retryCount++;
                if (retryCount >= HIS_CACHE_WAIT_RETRIES) {
                    log.info("科室[{}]删除操作获取锁失败，已重试{}次，当前线程退出", deptCode, HIS_CACHE_WAIT_RETRIES);
                    return false;
                }
                log.debug("科室[{}]删除操作第{}次获取锁失败，{}ms后重试", deptCode, retryCount, HIS_CACHE_WAIT_INTERVAL_MS);
                TimeUnit.MILLISECONDS.sleep(HIS_CACHE_WAIT_INTERVAL_MS);
            }
            // 4. 校验科室是否存在（避免无效删除）
            DeptDictDo dept = baseMapper.selectDeptByCodeDB(deptCode);
            if (ObjectUtils.isEmpty(dept)) {
                log.warn("科室[{}]不存在，无需删除", deptCode);
                return false;
            }
            // 5. 执行删除操作（按顺序：先删数据库，再删ES，最后删MongoDB描述信息）
            int deleteCount = baseMapper.deleteDeptDictById(deptCode);
            if (deleteCount <= 0) {
                log.error("科室[{}]数据库删除失败", deptCode);
                return false;
            }

            // 6. 删除ES数据（若失败，通过@GlobalTransactional回滚数据库删除）
            elasticSearchService.delete(INDEX_NAME, deptCode);

            // 7. 同步删除MongoDB中的科室描述信息（若业务需要）
            boolean deleteDescResult = deptDescInfoService.deleteDeptInfoByCode(deptCode);
            if (!deleteDescResult) {
                log.warn("科室[{}]MongoDB描述信息删除失败，但不影响主删除流程", deptCode);
                // 若为核心数据，可在此处抛异常触发回滚
            }

            log.info("科室[{}]已成功从数据库、ES和MongoDB删除", deptCode);
            return true;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("科室[{}]删除操作被中断", deptCode, e);
            return false;
        } finally {
            // 释放锁
            releaseLock(deleteLock, isDeleteLocked);
        }
    }
    /**
     * 查询所有科室数据（从数据库）
     * @return 科室列表
     */
    @Override
    public List<DeptDictDo> listDeptsDB() {
        List<DeptDictDo> deptList = baseMapper.selectDeptDB(); // 从数据库查询
        return deptList;
    }
    @Override
    public int deleteDeptDictByIds(List<String> deptcodes) {
        return this.deleteDeptDictByIds(deptcodes);
    }
    @Override
    public List<DeptDictVo> listESDepts() throws IOException {
        System.out.println("当前编码: " + System.getProperty("file.encoding")); // 应输出 UTF-8
        List<DeptDictVo> list =  elasticSearchService.searchAll(INDEX_NAME, DeptDictVo.class);
        for(DeptDictVo item:list){
            System.out.println(item.toString());
        }
        return list;
    }
    @Override
    public IPage<DeptDictDo> pageESDepts(int curPage, int pageSize) throws IOException {
        int validPage = Math.max(curPage, 1); // 页码最小为1
        int validSize = Math.max(pageSize, 10); // 条数最小为10，可参考ES服务的DEFAULT_SIZE
        EsPageResult<DeptDictDo> result = elasticSearchService.searchWithAllTotal(
                INDEX_NAME,
                validPage,
                validSize,
                "deptName",
                "asc",
                null,
                DeptDictDo.class);
        IPage<DeptDictDo> resultPage = new Page<>(curPage, pageSize);
        resultPage.setRecords(result.getRecords());
        resultPage.setTotal(result.getTotal());
        return resultPage;
    }
    /**
     * ES索引初始化
     * @throws IOException
     */
    public void initIndexAndMapping() throws IOException {
        try {
            if (elasticSearchService.existsIndex(INDEX_NAME)) {
                log.debug("Elasticsearch索引 [{}] 已存在，无需初始化。", INDEX_NAME);
                return;
            }
            log.info("Elasticsearch索引 [{}] 不存在，开始创建...", INDEX_NAME);
            esClient.indices().create(CreateIndexRequest.of(c -> c.index(INDEX_NAME)));
            log.info("索引 [{}] 创建成功。", INDEX_NAME);
            TimeUnit.MILLISECONDS.sleep(100);
            esClient.indices().putMapping(PutMappingRequest.of(p -> p
                    .index(INDEX_NAME)
                    .properties(generateMap())
            ));
            log.info("Elasticsearch索引 [{}] 映射设置完成。", INDEX_NAME);
        } catch (Exception e) {
            log.error("Elasticsearch索引初始化失败: {}", e.getMessage(), e);
            try {
                if (elasticSearchService.existsIndex(INDEX_NAME)) {
                    elasticSearchService.deleteIndex(INDEX_NAME);
                    log.warn("已删除创建失败的索引 [{}]。", INDEX_NAME);
                }
            } catch (Exception deleteEx) {
                log.error("清理失败索引时出错: {}", deleteEx.getMessage(), deleteEx);
            }
            throw new IOException("索引初始化失败，请检查ES服务和网络连接。", e);
        }
    }
    /**
     * 创建Map对照
     * @return
     */
    private Map<String, Property> generateMap() {
        Map<String, Property> properties = new HashMap<>(6);
        properties.put("deptCode", Property.of(p -> p.keyword(k -> k)));
        properties.put("deptName", Property.of(p -> p.text(t -> t.analyzer("ik_max_word").searchAnalyzer("ik_smart"))));
        properties.put("deptAlias", Property.of(p -> p.text(t -> t.analyzer("ik_max_word").searchAnalyzer("ik_smart"))));
        properties.put("inputCode", Property.of(p -> p.keyword(k -> k)));
        properties.put("hospitalId", Property.of(p -> p.keyword(k -> k)));
        properties.put("serialNo", Property.of(p -> p.long_(l -> l)));
        return properties;
    }
    /**
     * 同步锁 释放
     * @param lock
     * @param isLocked
     */
    private void releaseLock(RLock lock, boolean isLocked) {
        if (isLocked && lock.isHeldByCurrentThread()) {
            try {
                lock.unlock();
                log.debug("锁 [{}] 已释放", lock.getName());
            } catch (IllegalMonitorStateException e) {
                log.warn("释放锁 [{}] 失败，可能已过期", lock.getName(), e);
            }
        }
    }

    /**
     * 递归列表
     */
    private void recursionFn(List<DeptDictDo> list, DeptDictDo t)
    {
        // 得到子节点列表
        List<DeptDictDo> childList = getChildList(list, t);
        t.setChildren(childList);
        for (DeptDictDo tChild : childList)
        {
            if (hasChild(list, tChild))
            {
                recursionFn(list, tChild);
            }
        }
    }
    /**
     * 得到子节点列表
     */
    private List<DeptDictDo> getChildList(List<DeptDictDo> list, DeptDictDo t)
    {
        List<DeptDictDo> tlist = new ArrayList<DeptDictDo>();
//        Iterator<DeptDictDo> it = list.iterator();
//        while (it.hasNext())
//        {
//            DeptDictDo n = (DeptDictDo) it.next();
//            if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().equals(t.getDeptId()))
//            {
//                tlist.add(n);
//            }
//        }
        return tlist;
    }
    /**
     * 判断是否有子节点
     */
    private boolean hasChild(List<DeptDictDo> list, DeptDictDo t)
    {
        return getChildList(list, t).size() > 0 ? true : false;
    }
}